package com.dagger.utils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.DataWithMediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilder;

import lombok.extern.slf4j.Slf4j;

/**
 * SSE工具类
 * 
 * @author dagger
 *
 */
@Slf4j
public class SseEmitterUtil extends AbstractMsgUtil<SseEmitter> {

	/**
	 * 创建用户连接并返回 SseEmitter
	 *
	 * @param employeeCode 用户ID
	 * @return SseEmitter
	 */
	public SseEmitter connect() {
		// 设置超时时间，0表示不过期。nginx配置的600s，超过时间未完成会抛出异常：AsyncRequestTimeoutException
		SseEmitter sseEmitter = new SseEmitter(600 * 1000L);
		// 注册回调
		sseEmitter.onCompletion(completionCallBack(sseEmitter));
		return sseEmitter;
	}

	/**
	 * 给指定用户发送信息-单条
	 *
	 * @param employeeCode
	 * @param jsonMsg
	 */
	@Override
	public boolean sendMessage(SseEmitter sseEmitter, Object message) {
		try {
			sseEmitter.send(new SsePushBuilderImpl().data(message, MediaType.APPLICATION_JSON));
			return true;
		} catch (IOException e) {
			if (!ExceptionUtil.getExceptionMessage(e).contains("ResponseBodyEmitter has already completed")) {
				log.error(e.getMessage(), e);
			}
			return false;
		}
	}

	/**
	 * 移除用户连接
	 */
	@Override
	public void _removeUser(SseEmitter sseEmitter) {
	}

	private Runnable completionCallBack(SseEmitter sseEmitter) {
		return () -> {
			removeUser(sseEmitter);
		};
	}

	@Override
	public void _addUser(String msgKey, SseEmitter user) {
	}
}

class SsePushBuilderImpl implements SseEventBuilder {
	private static final MediaType TEXT_PLAIN = new MediaType("text", "plain", StandardCharsets.UTF_8);
	private final Set<DataWithMediaType> dataToSend = new LinkedHashSet<>(4);

	@Nullable
	private StringBuilder sb;

	@Override
	public SseEventBuilder id(String id) {
		append("id:").append(id).append('\n');
		return this;
	}

	@Override
	public SseEventBuilder name(String name) {
		append("event:").append(name).append('\n');
		return this;
	}

	@Override
	public SseEventBuilder reconnectTime(long reconnectTimeMillis) {
		append("retry:").append(String.valueOf(reconnectTimeMillis)).append('\n');
		return this;
	}

	@Override
	public SseEventBuilder comment(String comment) {
		append(':').append(comment).append('\n');
		return this;
	}

	@Override
	public SseEventBuilder data(Object object) {
		return data(object, null);
	}

	@Override
	public SseEventBuilder data(Object object, @Nullable MediaType mediaType) {
//		append("data:");
		saveAppendedText();
		this.dataToSend.add(new DataWithMediaType(object, mediaType));
		append('\n');
		return this;
	}

	SsePushBuilderImpl append(String text) {
		if (this.sb == null) {
			this.sb = new StringBuilder();
		}
		this.sb.append(text);
		return this;
	}

	SsePushBuilderImpl append(char ch) {
		if (this.sb == null) {
			this.sb = new StringBuilder();
		}
		this.sb.append(ch);
		return this;
	}

	@Override
	public Set<DataWithMediaType> build() {
		if (!StringUtils.hasLength(this.sb) && this.dataToSend.isEmpty()) {
			return Collections.emptySet();
		}
		append('\n');
		saveAppendedText();
		return this.dataToSend;
	}

	private void saveAppendedText() {
		if (this.sb != null) {
			this.dataToSend.add(new DataWithMediaType(this.sb.toString(), TEXT_PLAIN));
			this.sb = null;
		}
	}
}